﻿//RemoteStorage.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.Algo.History.Hydra
{
	using System;
	using System.Collections.Generic;
	using System.IO;
	using System.Linq;

	using Ecng.Collections;
	using Ecng.Common;

	using StockSharp.Logging;
	using StockSharp.Algo.Storages;
	using StockSharp.BusinessEntities;

	/// <summary>
	/// Доступные разрешения работы с данными, которые получает клиент.
	/// </summary>
	[Flags]
	public enum RemoteStoragePermissions
	{
		/// <summary>
		/// Сохранение данных.
		/// </summary>
		Save = 1,

		/// <summary>
		/// Загрузка данных.
		/// </summary>
		Load = Save << 1,

		/// <summary>
		/// Удаление данных.
		/// </summary>
		Delete = Load << 1,
	}

	/// <summary>
	/// Внешнее хранилище маркет-данных, доступ к которому организован через сетевое соединение WCF (подробнее, <see cref="System.ServiceModel"/>).
	/// </summary>
	[ErrorLogging]
	public abstract class RemoteStorage : BaseLogReceiver<RemoteStorage>, IRemoteStorage
	{
		private readonly SynchronizedDictionary<Guid, RemoteStoragePermissions> _sessions = new SynchronizedDictionary<Guid, RemoteStoragePermissions>();

		/// <summary>
		/// Инициализировать <see cref="RemoteStorage"/>.
		/// </summary>
		/// <param name="storageRegistry">Хранилище маркет-данных.</param>
		/// <param name="entityRegistry">Хранилище торговых сущностей.</param>
		protected RemoteStorage(IStorageRegistry storageRegistry, IEntityRegistry entityRegistry)
		{
			if (storageRegistry == null)
				throw new ArgumentNullException("storageRegistry");

			if (entityRegistry == null)
				throw new ArgumentNullException("entityRegistry");

			StorageRegistry = storageRegistry;
			EntityRegistry = entityRegistry;
		}

		/// <summary>
		/// Хранилище маркет-данных.
		/// </summary>
		public IStorageRegistry StorageRegistry { get; private set; }

		/// <summary>
		/// Хранилище торговых сущностей.
		/// </summary>
		public IEntityRegistry EntityRegistry { get; private set; }

		/// <summary>
		/// Проверить логин и пароль на правильность.
		/// </summary>
		/// <param name="login">Логин.</param>
		/// <param name="password">Пароль.</param>
		/// <returns>Допустимые действия.</returns>
		protected abstract RemoteStoragePermissions ValidateCredentials(string login, string password);

		/// <summary>
		/// Получить список доступных хранилищ маркет-данных.
		/// </summary>
		/// <returns>Хранилища маркет-данных.</returns>
		protected abstract IEnumerable<IMarketDataDrive> GetDrives();

		private void CheckSession(Guid sessionId, RemoteStoragePermissions permission)
		{
			var sessionPermissions = _sessions.TryGetValue2(sessionId);

			if (sessionPermissions == null)
				throw new UnauthorizedAccessException("Сессия с идентификатором {0} не найдена.".Put(sessionId));

			if (!sessionPermissions.Value.Contains(permission))
				throw new UnauthorizedAccessException("Сессия не имеет прав на {0}.".Put(permission));
		}

		private Security ToSecurity(string securityId, bool check = true)
		{
			var security = EntityRegistry.Securities.ReadById(securityId);

			if (check && security == null)
				throw new InvalidOperationException("Инструмент {0} не найден.".Put(securityId));

			return security;
		}

		private IMarketDataStorageDrive GetStorageDrive(string securityId, string dataType, object arg)
		{
			var security = ToSecurity(securityId);
			return GetStorage(security, dataType, arg, GetDrives().First()).GetStorageDrive();
		}

		private IMarketDataStorage GetStorage(string securityId, string dataType, object arg, DateTime date)
		{
			var security = ToSecurity(securityId);
			return GetDrives().Select(drive => GetStorage(security, dataType, arg, drive)).FirstOrDefault(storage => storage.Dates.Contains(date));
		}

		private IMarketDataStorage GetStorage(Security security, string dataType, object arg, IMarketDataDrive drive)
		{
			return StorageRegistry.GetStorage(security, dataType.To<Type>(), arg, drive);
		}

		Guid IRemoteStorage.Login(string login, string password)
		{
			var sessionPermissions = ValidateCredentials(login, password);

			var sessionId = Guid.NewGuid();
			_sessions.Add(sessionId, sessionPermissions);

			this.AddInfoLog("Сессия {0}. Создание для юзера {1}.", sessionId, login);

			return sessionId;
		}

		void IRemoteStorage.Logout(Guid sessionId)
		{
			_sessions.Remove(sessionId);
			this.AddInfoLog("Сессии {0}. Окончание.", sessionId);
		}

		string[] IRemoteStorage.GetAllSecurityIds(Guid sessionId)
		{
			CheckSession(sessionId, RemoteStoragePermissions.Load);
			this.AddInfoLog("Сессии {0}. Получение всех инструментов.", sessionId);
			return EntityRegistry.Securities.Select(s => s.Id).ToArray();
		}

		Security[] IRemoteStorage.GetSecurities(Guid sessionId, string[] securityIds)
		{
			CheckSession(sessionId, RemoteStoragePermissions.Load);
			this.AddInfoLog("Сессии {0}. Получение инструментов.", sessionId);

			if (securityIds == null)
				throw new ArgumentNullException("securityIds");

			return securityIds
				.Select(id => ToSecurity(id, false))
				.Where(s => s != null)
				.ToArray();
		}

		DateTime[] IRemoteStorage.GetDates(Guid sessionId, string securityId, string dataType, object arg)
		{
			CheckSession(sessionId, RemoteStoragePermissions.Load);
			this.AddInfoLog("Сессии {0}. Получение дат для {1} {2} {3}.", sessionId, securityId, dataType, arg);

			var security = ToSecurity(securityId, false);

			return security == null
			       	? new DateTime[0]
			       	: GetDrives()
			       	  	.SelectMany(drive => GetStorage(security, dataType, arg, drive).Dates)
			       	  	.Distinct()
			       	  	.OrderBy()
			       	  	.ToArray();
		}

		Tuple<Type, object[]>[] IRemoteStorage.GetCandleTypes(Guid sessionId, string securityId)
		{
			CheckSession(sessionId, RemoteStoragePermissions.Load);
			this.AddInfoLog("Сессии {0}. Получение типов свечек для {1}.", sessionId, securityId);

			var security = ToSecurity(securityId, false);

			if (security == null)
				return new Tuple<Type, object[]>[0];

			return GetDrives()
						.SelectMany(drive => drive.GetCandleTypes(security))
						.GroupBy(t => t.Item1)
						.Select(g => new Tuple<Type, object[]>(g.Key, g.SelectMany(t => t.Item2).Distinct().ToArray()))
						.ToArray();
		}

		void IRemoteStorage.Save(Guid sessionId, string securityId, string dataType, object arg, DateTime date, byte[] data)
		{
			CheckSession(sessionId, RemoteStoragePermissions.Save);
			this.AddInfoLog("Сессии {0}. Сохранение для {1} {2} {3} за {4}.", sessionId, securityId, dataType, arg, date);

			GetStorageDrive(securityId, dataType, arg).Save(date, data);
		}

		void IRemoteStorage.Delete(Guid sessionId, string securityId, string dataType, object arg, DateTime date)
		{
			CheckSession(sessionId, RemoteStoragePermissions.Delete);
			this.AddInfoLog("Сессии {0}. Удаление для {1} {2} {3} за {4}.", sessionId, securityId, dataType, arg, date);

			GetStorage(securityId, dataType, arg, date).Delete(date);
		}

		byte[] IRemoteStorage.Load(Guid sessionId, string securityId, string dataType, object arg, DateTime date)
		{
			CheckSession(sessionId, RemoteStoragePermissions.Load);
			this.AddInfoLog("Сессии {0}. Загрузка для {1} {2} {3} за {4}.", sessionId, securityId, dataType, arg, date);

			var storage = GetStorage(securityId, dataType, arg, date);
			return storage == null ? null : storage.GetStorageDrive().Load(date);
		}

		Stream IRemoteStorage.LoadStream(Guid sessionId, string securityId, string dataType, object arg, DateTime date)
		{
			CheckSession(sessionId, RemoteStoragePermissions.Load);
			this.AddInfoLog("Сессии {0}. Загрузка потока для {1} {2} {3} за {4}.", sessionId, securityId, dataType, arg, date);

			var storage = GetStorage(securityId, dataType, arg, date);
			return storage == null ? Stream.Null : storage.GetStorageDrive().LoadStream(date);
		}

		void IRemoteStorage.ClearDatesCache(Guid sessionId, string securityId, string dataType, object arg)
		{
			CheckSession(sessionId, RemoteStoragePermissions.Load);
			this.AddInfoLog("Сессии {0}. Очистка кэша для {1} {2} {3}.", sessionId, securityId, dataType, arg);

			GetStorageDrive(securityId, dataType, arg).ClearDatesCache();
		}
	}
}